import express, {
  Express,
  Request as ExpressRequest,
  Response as ExpressResponse,
  NextFunction,
} from "express";
import { Logger } from "./logger";
import path from "path";
import { DESIGN_PARAMTYPES, INJECTED_TOKENS } from "../common/constants";
import { GlobalHttpExceptionFilter } from "../common/http-exception.filter";
import { defineModule } from "../common/module.decorator";
import {
  APP_FILTER,
  DECORATORYFACTORY,
  APP_PIPE,
  FORBIDDEN_RESOURECE,
  APP_GUARD,
} from "./constants";
import {
  PipeTransform,
  ExecutionContext,
  CanActivate,
  ForbiddenException,
  RequestMethod,
  ExceptionFilter,
} from "@nestjs/common";
import { Reflector } from "./reflector";
import { from, mergeMap, Observable, of } from "rxjs";
export class NestApplication {
  //在它的内部私有化一个Express的实例
  private readonly app: Express = express();
  //在此处保存所有的provider的实例key就是token 值就是类的实例或者值
  private readonly providerInstances = new Map();
  //记录每个模块里有哪些provider的token
  private readonly moduleProviders: any = new Map();
  //存放全局可用的tokens
  private readonly globalProviders: any = new Map();
  //记录所有的中间件
  private readonly middlewares = [];
  //记录要排除的路径
  private readonly excludeRoutes = [];
  //全局异常过滤器
  private readonly defaultGlobalHttpExceptionFilter =
    new GlobalHttpExceptionFilter();
  // 这里存放着所有的全局异常过滤器
  private readonly globalHttpExceptionFilters: ExceptionFilter[] = [];
  //这里存放着所有的全局管道
  private readonly globalPipes: PipeTransform[] = [];
  private readonly globalGuards = [];
  constructor(protected readonly module) {
    this.app.use(express.json()); //用来把JSON格式的请求体对象放在req.body上
    this.app.use(express.urlencoded({ extended: true })); // 把form表单格式的请求体对象放在req.body上
  }
  private initMiddlewares() {
    //调用配置中间件的方法 MiddlewareConsumer就是当前NestApplication的实例
    this.module.prototype.configure?.(this);
  }
  useGlobalPipes(...pipe) {
    this.globalPipes.push(...pipe);
  }
  useGlobalFilters(...filter) {
    defineModule(
      this.module,
      filter.filter((filter) => filter instanceof Function)
    );
    this.globalHttpExceptionFilters.push(...filter);
  }
  exclude(...routeInfos): this {
    this.excludeRoutes.push(...routeInfos.map(this.normalizeRouteInfo));
    return this;
  }
  apply(...middlewares) {
    defineModule(this.module, middlewares);
    this.middlewares.push(...middlewares);
    return this;
  }
  getMiddlewareInstance(middleware) {
    if (middleware instanceof Function) {
      const dependencies = this.resolveDependencies(middleware);
      return new middleware(...dependencies);
    }
    return middleware;
  }
  isExcluded(routePath, routeMethod) {
    return this.excludeRoutes.some(
      (excludeRoute) =>
        excludeRoute.path === routePath &&
        (excludeRoute.method === RequestMethod.ALL ||
          excludeRoute.method === routeMethod)
    );
  }
  forRoutes(...routes) {
    for (const route of routes) {
      for (const midlleware of this.middlewares) {
        const { routePath, routeMethod } = this.normalizeRouteInfo(route);
        this.app.use(routePath, (req, res, next) => {
          if (this.isExcluded(routePath, routeMethod)) {
            return next();
          }
          if (routeMethod === RequestMethod.ALL || routeMethod === req.method) {
            if ("use" in midlleware.prototype || "use" in midlleware) {
              const middlewareInstance = this.getMiddlewareInstance(midlleware);
              middlewareInstance.use(req, res, next);
            } else if (midlleware instanceof Function) {
              midlleware(req, res, next);
            } else {
              next();
            }
          } else {
            next();
          }
        });
      }
    }
    this.middlewares.length = 0;
    return this;
  }
  private normalizeRouteInfo(route) {
    let routePath = ""; //转化路径
    let routeMethod = RequestMethod.ALL; //默认是支持所有的方法
    if (typeof route === "string") {
      routePath = route; //传的就是一个路径
    } else if (typeof route === "object") {
      routePath = route.path;
      routeMethod = route.method ?? RequestMethod.ALL;
    } else if (route instanceof Function) {
      routePath = Reflect.getMetadata("prefix", route);
    }
    // cats => /cats
    routePath = path.posix.join("/", routePath);
    return { routePath, routeMethod };
  }
  private registerProvidersFromModule(module, ...parentModules) {
    //获取导入的全局模块
    const global = Reflect.getMetadata("global", module);
    //获取导入模块中的元数据提供
    //1.有可能导入的模块只导出了一部分 并没有全量导出 所以需要exports进行过滤
    //遍历导出的exports数组
    const exports = Reflect.getMetadata("exports", module) ?? [];
    const importedProviders = Reflect.getMetadata("providers", module) ?? [];
    for (const exportToken of exports) {
      //exports 可能还有Module的情况
      if (this.isModule(exportToken)) {
        this.registerProvidersFromModule(exportToken, module, ...parentModules);
      } else {
        const provider = importedProviders.find(
          (provider) =>
            provider.provide === exportToken || provider === exportToken
        );
        if (provider) {
          [module, ...parentModules].forEach((module) => {
            this.addProvider(provider, module, global);
          });
        }
      }
    }
    this.initController(module);
  }
  private addDefaultproviders() {
    this.addProvider(Reflector, this.module, true);
  }
  //初始化提供化
  async initProviders() {
    this.addDefaultproviders();
    const imports = Reflect.getMetadata("imports", this.module) ?? [];
    for (const importModule of imports) {
      let importedModule = importModule;
      if (importModule instanceof Promise) {
        importedModule = await importedModule;
      }
      //如果导入的模块有module属性 说明这是一个动态模块
      if ("module" in importedModule) {
        const { module, providers, exports, controllers } = importedModule;
        const oldProviders = Reflect.getMetadata("providers", module);
        const newProviders = [...(oldProviders ?? []), ...(providers ?? [])];
        defineModule(module, newProviders);
        const newControllers = [
          ...(module.controllers ?? []),
          ...(controllers ?? []),
        ];
        defineModule(module, newControllers);
        const oldExports = Reflect.getMetadata("exports", module);
        const newExports = [...(oldExports ?? []), ...(exports ?? [])];
        Reflect.defineMetadata("controllers", newControllers, module);
        Reflect.defineMetadata("providers", newProviders, module);
        Reflect.defineMetadata("exports", newExports, module);
        this.registerProvidersFromModule(module, this.module);
      } else {
        this.registerProvidersFromModule(importedModule);
      }
    }
    //获取当前模块的元数据
    const providers = Reflect.getMetadata("providers", this.module) ?? [];
    for (const provider of providers) {
      this.addProvider(provider, this.module);
    }
  }
  private isModule(exportToken) {
    return (
      exportToken &&
      exportToken instanceof Function &&
      Reflect.getMetadata("module", exportToken)
    );
  }
  addProvider(provider, module, global = false) {
    //此provider代表module这个模块对应的provider的token
    const providers = global
      ? this.globalProviders
      : this.moduleProviders.get(module) || new Set();
    if (!global) {
      this.moduleProviders.set(module, providers);
    }
    const injectToken = provider.provide ?? provider;
    if (this.providerInstances.has(injectToken)) {
      providers.add(injectToken);
      return;
    }
    if (provider.provide && provider.useClass) {
      //解析控制器的依赖
      const dependencies = this.resolveDependencies(provider.useClass);
      //创建类的实例
      const classInstance = new provider.useClass(...dependencies);
      //把provider的token和类的实例来保存到this.providers里
      this.providerInstances.set(provider.provide, classInstance);
      providers.add(provider.provide);
    } else if (provider.provide && provider.useValue) {
      //提供的是一个值 则不需要容器帮助实例化 直接使用此值注册就可以了
      this.providerInstances.set(provider.provide, provider.useValue);
      providers.add(provider.provide);
    } else if (provider.provide && provider.useFactory) {
      const inject = provider.inject ?? [];
      const injectedValues = inject.map((injectToken) =>
        this.getProviderByToken(injectToken, module)
      );
      const value = provider.useFactory(...injectedValues);
      this.providerInstances.set(provider.provide, value);
      providers.add(provider.provide);
    } else {
      const dependencies = this.resolveDependencies(provider);
      //表示只提供了一个类 token是这个类  值是这个类的实例
      this.providerInstances.set(provider, new provider(...dependencies));
      providers.add(provider);
    }
  }
  // initProviders() {
  //   //取出模块里面所有的provider
  //   const providers = Reflect.getMetadata("providers", this.module) ?? [];
  //   for (const provider of providers) {
  //     if (provider.provide && provider.useClass) {
  //       //解析控制器的依赖
  //       const dependencies = this.resolveDependencies(provider.useClass);
  //       //创建类的实例
  //       const classInstance = new provider.useClass(...dependencies);
  //       //把provider的token和类的实例来保存到this.providers里
  //       this.providers.set(provider.provide, classInstance);
  //     } else if (provider.provide && provider.useValue) {
  //       //提供的是一个值 则不需要容器帮助实例化 直接使用此值注册就可以了
  //       this.providers.set(provider.provide, provider.useValue);
  //     } else if (provider.provide && provider.useFactory) {
  //       const inject = provider.inject ?? [];
  //       const injectedValues = inject.map(this.getProviderByToken);
  //       const value = provider.useFactory(...injectedValues);
  //       this.providers.set(provider.provide, value);
  //     } else {
  //       const dependencies = this.resolveDependencies(provider);
  //       //表示只提供了一个类 token是这个类  值是这个类的实例
  //       this.providers.set(provider, new provider(...dependencies));
  //     }
  //   }
  // }
  use(middleware) {
    this.app.use(middleware);
  }
  private getProviderByToken = (injectToken, module) => {
    //如何通过token在特定模块下找到对应的provider
    //先找到此模块对应的token set 在判断此injectToken是否在set中 如果存在则返回对应provider的实例
    if (
      this.moduleProviders.get(module)?.has(injectToken) ||
      this.globalProviders.has(injectToken)
    ) {
      return this.providerInstances.get(injectToken);
    } else {
      return null;
    }
  };
  private resolveDependencies(Clazz) {
    //取得注入的token
    const injectedTokens = Reflect.getMetadata(INJECTED_TOKENS, Clazz) ?? [];
    //获取构造函数的参数类型
    const constructorParams =
      Reflect.getMetadata(DESIGN_PARAMTYPES, Clazz) ?? [];
    return constructorParams.map((param, index) => {
      const module = Reflect.getMetadata("nestModule", Clazz);
      //把每一个param的token默认换成对应的provider的值
      return this.getProviderByToken(injectedTokens[index] ?? param, module);
    });
  }

  private getGuardsInstance(guard) {
    if (guard instanceof Function) {
      const dependencies = this.resolveDependencies(guard);
      return new guard(...dependencies);
    }
    return guard;
  }

  async callguards(guards: CanActivate[], context: ExecutionContext) {
    for (const guard of guards) {
      const guardInstance = this.getGuardsInstance(guard);
      const canActivate = await guardInstance.canActivate(context);
      if (!canActivate) {
        throw new ForbiddenException(FORBIDDEN_RESOURECE);
      }
    }
    return true;
  }

  getInterceptorInstance(interceptor) {
    if (interceptor instanceof Function) {
      const dependencies = this.resolveDependencies(interceptor);
      return new interceptor(...dependencies);
    }
    return interceptor;
  }

  callInterceptors(
    controller,
    method,
    args,
    interceptors,
    context,
    host,
    pipes
  ) {
    const nextFn = (i = 0): Observable<any> => {
      if (i >= interceptors.length) {
        const result = method.apply(controller, ...args);
        return result instanceof Observable ? from(result) : of(result);
      }
      const handler = {
        handler: () => nextFn(i + 1),
      };
      const interceptor = this.getInterceptorInstance(interceptors[i]);
      const result = interceptor.intercept(context, handler);
      return from(result).pipe(
        mergeMap((result) =>
          result instanceof Observable ? result : of(result)
        )
      );
    };
    return nextFn();
  }
  async initController(module) {
    //取出模块里面所有的控制器 然后做好路由配置
    const controllers = Reflect.getMetadata("controllers", module);
    Logger.log(`AppModule dependencies initialized`, "InstanceLoader");
    for (const Controller of controllers) {
      //解析控制器的依赖
      const dependencies = this.resolveDependencies(Controller);
      const controller = new Controller(...dependencies);
      //取出控制器里面的路由信息
      const prefix = Reflect.getMetadata("prefix", Controller) || "/";
      //开始解析路由
      Logger.log(`${Controller.name} {${prefix}}`, "RouteRosolver");
      const controllerPrototype = Controller.prototype;
      const controllerPipes = Reflect.getMetadata("pipes", Controller) || [];
      const controllerGuards = Reflect.getMetadata("guards", Controller) || [];
      const controllerInterceptors =
        Reflect.getMetadata("interceptors", Controller) || [];
      //获取控制器上绑定的异常过滤取数组
      const controllerFilters =
        Reflect.getMetadata("filters", Controller) ?? [];
      defineModule(this.module, controllerFilters);
      //遍历原型上的方法名
      for (const methodName of Object.getOwnPropertyNames(
        controllerPrototype
      )) {
        //取得原型上绑定的方法
        const method = controllerPrototype[methodName];
        //取得此函数上绑定的方法名的元数据
        const httpMethod = Reflect.getMetadata("method", method);
        //取得此函数上绑定的路径的元数据
        const pathMetadata = Reflect.getMetadata("path", method);
        const redirectUrl = Reflect.getMetadata("redirectUrl", method);
        const redirectStatusCode = Reflect.getMetadata(
          "redirectStatusCode",
          method
        );
        const statusCode = Reflect.getMetadata("statusCode", method);
        const headers = Reflect.getMetadata("headers", method) ?? [];
        //如果方法名不存在 则不处理
        //获得拦截器数组
        const methodInterceptors =
          Reflect.getMetadata("interceptors", method) ?? [];
        //获取方法的过滤器数组
        const methodFilters = Reflect.getMetadata("filters", method) ?? [];
        //获取控制器上绑定的管道数组
        const methodPipes = Reflect.getMetadata("pipes", method) ?? [];
        const methodGuards = Reflect.getMetadata("guards", method) || [];
        const pipes = [...methodPipes, ...controllerPipes];
        const interceptors = [...controllerInterceptors, ...methodInterceptors];
        const guards = [
          ...this.globalGuards,
          ...methodGuards,
          ...controllerGuards,
        ];
        defineModule(this.module, methodFilters);
        //合并方法和控制器上的过滤器
        if (!httpMethod) continue;
        //拼出来完整的路由路径
        const routePath = path.posix.join("/", prefix, pathMetadata);
        //配置路由 当客户端以httpMethod方法请求routePath路径的时候 会由对应的函数进行处理
        this.app[httpMethod.toLowerCase()](
          routePath,
          async (
            req: ExpressRequest,
            res: ExpressResponse,
            next: NextFunction
          ) => {
            const host = {
              switchToHttp: () => ({
                getRequest(): ExpressRequest {
                  return req;
                },
                getResponse(): ExpressResponse {
                  return res;
                },
                getNext(): NextFunction {
                  return next;
                },
              }),
            };
            const context: ExecutionContext = {
              ...host,
              getClass: () => Controller,
              getHandler: () => method,
            };
            try {
              await this.callguards(guards, context);
              const args = await this.resolveParams(
                controller,
                methodName,
                context,
                host,
                pipes
              );
              this.callInterceptors(
                controller,
                method,
                args,
                interceptors,
                context,
                host,
                pipes
              ).subscribe({
                next: (result) => {
                  //判断如果需要重定向, 则直接重定向到指定的redirectUrl
                  if (result?.url) {
                    return res.redirect(result.statusCode || 302, result.url);
                  }
                  //判断如果需要重定向 则直接重定向到指定redirectUrl
                  if (redirectUrl) {
                    return res.redirect(redirectStatusCode || 302, redirectUrl);
                  }
                  if (statusCode) {
                    res.statusCode = statusCode;
                  } else if (httpMethod === "POST") {
                    res.statusCode = 201;
                  }
                  const responseMetadata = this.getResponseMetadata(
                    controller,
                    methodName
                  );
                  if (
                    !responseMetadata ||
                    responseMetadata?.data?.passthrough
                  ) {
                    headers.forEach((name, value) => {
                      res.setHeader({ name, value });
                    });
                    //把返回值序列化返回给客户端
                    res.send(result);
                  }
                },
                error: (err) => {
                  //如果处理函数执行过程中抛出了异常 则交给异常过滤器进行处理
                  this.callExceptionFilter(
                    err,
                    host,
                    methodFilters,
                    controllerFilters
                  );
                },
              });
              //执行路由处理函数 获取返回值
            } catch (error) {
              await this.callExceptionFilter(
                error,
                host,
                methodFilters,
                controllerFilters
              );
            }
          }
        );
        Logger.log(
          `Mapped {${routePath} ,${httpMethod}} route`,
          "RoutesResolver"
        );
      }
    }
    Logger.log(`Nest application successfully stared`, "NestApplication");
  }

  getFilterInstance(filter) {
    if (filter instanceof Function) {
      const dependencies = this.resolveDependencies(filter);
      return new filter(...dependencies);
    }
    return filter;
  }

  private callExceptionFilter(error, host, methodFilters, controllerFilters) {
    // 按方法过滤器、控制器过滤器、用户配置全局过滤器和默认全局过滤的顺序进行遍历 找到第一个能处理这个错误的过滤器进行处理就可以了
    const allFilters = [
      ...methodFilters,
      ...controllerFilters,
      ...this.globalHttpExceptionFilters,
      this.defaultGlobalHttpExceptionFilter,
    ];
    for (const filter of allFilters) {
      let filterInstance = this.getFilterInstance(filter);
      //去除此异常过滤器关心的异步或者要处理的异常
      const exceptions =
        Reflect.getMetadata("catch", filterInstance.constructor) ?? [];
      if (
        exceptions.length === 0 ||
        exceptions.some((exception) => error instanceof exception)
      ) {
        filterInstance.catch(error, host);
        break;
      }
    }
  }

  private getResponseMetadata(controller: any, methodName: any) {
    const paramsMetaData =
      Reflect.getMetadata("params", controller, methodName) ?? [];
    return paramsMetaData
      .filter(Boolean)
      .find(
        (param) =>
          param.key === `Response` ||
          param.key === "Res" ||
          param.key === "Next"
      );
  }

  private resolveParams(
    instance: any,
    methodName: string,
    context,
    host,
    pipes: PipeTransform[]
  ) {
    const { getRequest, getResponse, getNext } = context;
    const req = getRequest();
    const res = getResponse();
    const next = getNext();
    const paramsMetaData =
      Reflect.getMetadata("params", instance, methodName) ?? [];
    return Promise.all(
      paramsMetaData.map(async (param) => {
        const { key, data, factory, pipes: paramPipes, metatype } = param;
        let value;
        switch (key) {
          case "Request":
          case "Req":
            value = req;
            break;
          case "Query":
            value = data ? req.query[data] : req.query;
            break;
          case "Headers":
            value = data ? req.headers[data] : req.headers;
            break;
          case "Session":
            value = data ? req.session[data] : req.session;
            break;
          case "Ip":
            value = req.ip;
            break;
          case "Param":
            value = data ? req.params[data] : req.params;
            break;
          case "Body":
            value = data ? req.body[data] : req.body;
            break;
          case "Response":
          case "res":
            value = res;
            break;
          case "Next":
            value = next;
            break;
          case DECORATORYFACTORY:
            value = factory(data, host);
            break;
          default:
            value = null;
            break;
        }
        for (const pipe of [...this.globalPipes, ...pipes, ...paramPipes]) {
          const pipeInstance = this.getPipeInstance(pipe);
          let type = key === DECORATORYFACTORY ? "custom" : key.toLowerCase();
          value = await pipeInstance.transform(value, { type, data, metatype });
        }
        return value;
      })
    );
  }
  private getPipeInstance(pipe) {
    if (pipe instanceof Function) {
      const dependencies = this.resolveDependencies(pipe);
      return new pipe(...dependencies);
    }
    return pipe;
  }
  async initGlobalFilters() {
    const providers = Reflect.getMetadata("providers", this.module) ?? [];
    for (const provider of providers) {
      if (provider.provide === APP_FILTER) {
        const providerInstance = this.getProviderByToken(
          APP_FILTER,
          this.module
        );
        this.useGlobalFilters(providerInstance);
      }
    }
  }

  async initGlobalPipes() {
    const providers = Reflect.getMetadata("providers", this.module) ?? [];
    for (const provider of providers) {
      if (provider.provide === APP_PIPE) {
        const providerInstance = this.getProviderByToken(APP_PIPE, this.module);
        this.useGlobalFilters(providerInstance);
      }
    }
  }

  async initGlobalGuards() {
    const providers = Reflect.getMetadata("providers", this.module) ?? [];
    for (const provider of providers) {
      if (provider.provide === APP_GUARD) {
        const providerInstance = this.getProviderByToken(APP_PIPE, this.module);
        this.useGlobalGuards(providerInstance);
      }
    }
  }

  useGlobalGuards(...guards) {
    this.globalGuards.push(...guards);
  }
  //启动http服务器
  async listen(port: number) {
    //初始化依赖注入
    await this.initProviders();
    //初始化中间件的方法
    await this.initMiddlewares();
    //初始化全局过滤器
    await this.initGlobalFilters();
    //配置路由
    await this.initController(this.module);
    await this.initGlobalGuards(); //初始化全局路由守卫
    //调用express实例的listen方法 启动一个HTTP服务器 监听port端口
    this.app.listen(port, () => {
      Logger.log(
        `Application is running on http://localhost:${port}`,
        "NestApplication"
      );
    });
  }
}
